package own.stu.inter.loadbalance;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import own.stu.inter.loadbalance.chooseRule.IChooseRule;
import own.stu.inter.loadbalance.chooseRule.RoundRobinRule;
import own.stu.inter.loadbalance.ping.IPing;
import own.stu.inter.loadbalance.ping.IPingStrategy;
import own.stu.inter.loadbalance.ping.ParallelPingStrategy;
import own.stu.inter.loadbalance.ping.PingService;
import own.stu.inter.loadbalance.server.ServerInstance;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LoadBalancer implements ILoadBalancer {

    private static Logger logger = LoggerFactory.getLogger(LoadBalancer.class);

    protected volatile List<ServerInstance> allServerList = Collections.synchronizedList(new ArrayList<>());
    protected volatile List<ServerInstance> activeServerList = Collections.synchronizedList(new ArrayList<>());
    protected ReadWriteLock allServerLock = new ReentrantReadWriteLock();
    protected ReadWriteLock activeServerLock = new ReentrantReadWriteLock();

    protected IChooseRule chooseRule;

    private PingService pingService;

    public LoadBalancer() {
        //设置默认选择策略，且不设置ping
        this(null, null);
    }

    public LoadBalancer(IChooseRule chooseRule, IPing ping) {
        this(chooseRule, ping, new ParallelPingStrategy());
    }

    public LoadBalancer(IChooseRule chooseRule, IPing ping, IPingStrategy pingStrategy) {
        if (chooseRule != null) {
            this.chooseRule = chooseRule;
        } else {
            this.chooseRule = new RoundRobinRule(this);
        }
        pingService = new PingService(this, ping, pingStrategy);
    }

    private void init() {
        pingService.init();
    }

    public void addServer(ServerInstance... newServer) {
        if (newServer == null || newServer.length == 0) {
            return;
        }

        addServers(Arrays.asList(newServer));
    }

    public void addServers(List<ServerInstance> newServers) {
        if (newServers != null && newServers.size() > 0) {
            try {
                ArrayList<ServerInstance> newList = new ArrayList<>();
                newList.addAll(allServerList);
                newList.addAll(newServers);
                setServersList(newList);
            } catch (Exception e) {
                logger.error("LoadBalancer addServer:  {} error", newServers, e);
            }
        }
    }

    public ServerInstance chooseServer() {
        return chooseServer(null);
    }

    public ServerInstance chooseServer(Object key) {

        if (chooseRule == null) {
            return null;
        } else {
            try {
                return chooseRule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer choosing server error", e);
                return null;
            }
        }
    }

    public void downServer(ServerInstance server) {
        if (server == null || !server.isAlive()) {
            return;
        }

        logger.error("LoadBalancer downServer {}", server);
        server.setAlive(false);
    }

    public void setServersList(List<ServerInstance> serversList) {
        Lock writeLock = allServerLock.writeLock();

        writeLock.lock();
        try {
            ArrayList<ServerInstance> allServers = new ArrayList<>();
            for (ServerInstance instance : serversList) {
                if (instance == null) {
                    continue;
                }

                logger.debug("LoadBalancer:  addServer [{}]", instance.getId());
                allServers.add(instance);
            }

            if (!allServerList.equals(allServers)) {
                allServerList = allServers;
                pingService.sendPing();
            }
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public List<ServerInstance> getActiveServers() {
        return Collections.unmodifiableList(activeServerList);
    }

    public void setActiveServerList(List<ServerInstance> activeServerList) {
        Lock wLock = activeServerLock.writeLock();
        wLock.lock();
        this.activeServerList = activeServerList;
        wLock.unlock();
    }

    @Override
    public List<ServerInstance> getAllServers() {
        return Collections.unmodifiableList(allServerList);
    }

    public ReadWriteLock getAllServerLock() {
        return allServerLock;
    }

    public ReadWriteLock getActiveServerLock() {
        return activeServerLock;
    }
}
